/*-
 * Copyright (c) 2006, 2007 FTS Team
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. Neither the name of the FTS Team nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      FileDocumentStorage.cpp
 *
 * $FTS$
 */
#include <FileDocumentStorage.hpp>
#include <FTSSException.hpp>
 #include <string.h>

#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

namespace FTSS // FullTextSearchSystem
{

//
// Check existence of a non-empty file
//
INT_32 FileDocumentStorage::NonEmptyFileExists(CCHAR_P szFile)
{
	struct stat oFileStat;
	if (stat(szFile, &oFileStat) == 0 && oFileStat.st_size != 0) { return 0; }

return -1;
}

//
// Open storage file
//
void FileDocumentStorage::OpenStorage(CCHAR_P szFile, const UINT_32 & iMode, const INT_32 & iIndexFile)
{
//	fprintf(stderr, "~~~~~~~~~~~~~~~~~~~~~~ OPENING STORAGE ~~~~~~~~~~~~~~~~~~\n");

	CCHAR_P szReadableFileType = NULL;
	UINT_32 iFileType          = 0;

	if (iIndexFile == 1)
	{
		szReadableFileType = C_DOC_STORAGE_IDX_READABLE;
		iFileType          = C_DOC_STORAGE_IDX_TYPE;
	}
	else
	{
		szReadableFileType = C_DOC_STORAGE_DATA_READABLE;
		iFileType          = C_DOC_STORAGE_DATA_TYPE;
	}

	INT_32 iHandle = 0;
	if      (iMode == C_READ_MODE)       { iHandle = open(szFile, O_RDONLY,           0644); mmapFlags = PROT_READ;}
	else if (iMode == C_WRITE_MODE)      { iHandle = open(szFile, O_WRONLY | O_CREAT, 0644); mmapFlags = PROT_WRITE;}
	else if (iMode == C_READ_WRITE_MODE) { iHandle = open(szFile, O_RDWR   | O_CREAT, 0644); mmapFlags = PROT_WRITE | PROT_READ;}

	struct stat statbuf;
	if (fstat(iHandle, &statbuf) < 0){
		throw UnixException(errno, "fstat");
	} else {
	}
	
//	fprintf(stderr, "\nSizeOfFile=%ld\n", statbuf.st_size); 
	if (iHandle == -1) { throw UnixException(errno, "open"); }
	UINT_32 iDummy = 0;
	CHAR_8 szReadable[C_FTSS_READABLE_LENGTH];

	if (NonEmptyFileExists(szFile) == 0)
	{
		// Read FTSS magic number
		if (read(iHandle, &iDummy, sizeof(UINT_32)) != sizeof(UINT_32)) { close(iHandle); throw UnixException(errno, "read"); }
		if (iDummy != C_FTSS_MAGIC) { close(iHandle); throw LogicException("Magic FTSS number is incorrect."); }

		// Read FTSS file type
		if (read(iHandle, &iDummy, sizeof(UINT_32)) != sizeof(UINT_32)) { close(iHandle); throw UnixException(errno, "read"); }
		if (iDummy != iFileType) { close(iHandle); throw LogicException("Magic file type number is incorrect."); }

		// Read FTSS human-readable file type
		memset(szReadable, 0, C_FTSS_READABLE_LENGTH);
//		fprintf(stderr, "READABLE LENGTH=%u ; iDummy=%u ; FTSS_READABLE_LENGTH=%u\n" ,C_FTSS_READABLE_LENGTH, iDummy, C_FTSS_READABLE_LENGTH);
		if (read(iHandle, szReadable, C_FTSS_READABLE_LENGTH) != C_FTSS_READABLE_LENGTH) { close(iHandle); throw UnixException(errno, "read"); }
		if (strcmp(szReadableFileType, szReadable) != 0) { close(iHandle); throw LogicException("Magic human-readable string is incorrect."); }

		if (iIndexFile == 1)
		{
			// Read number of records
			if (read(iHandle, &iRecords, sizeof(UINT_32)) != sizeof(UINT_32)) { close(iHandle); throw UnixException(errno, "read"); }
			// All okay after reading number of records
#ifdef _DEBUG
fprintf(stderr, "FileDocumentStorage::OpenStorage records = %d\n", iRecords);
#endif
		}
	}
	// Write header
	else
	{
		if (iMode == C_READ_MODE) { close(iHandle); throw LogicException("Cannot use EMPTY file in read-only mode"); }

		// Read/Write or Write-only mode below
		iDummy = C_FTSS_MAGIC;
		// Write Index Header
		if (write(iHandle, &iDummy, sizeof(UINT_32)) != sizeof(UINT_32)) { close(iHandle); throw UnixException(errno, "write"); }

		// Write Index Header
		iDummy = iFileType;
		if (write(iHandle, &iDummy, sizeof(UINT_32)) != sizeof(UINT_32)) { close(iHandle); throw UnixException(errno, "write"); }

		// Write FTSS human-readable file type
		memset(szReadable, 0, C_FTSS_READABLE_LENGTH);
		snprintf(szReadable, C_FTSS_READABLE_LENGTH, "%s", szReadableFileType);
		if (write(iHandle, szReadable, C_FTSS_READABLE_LENGTH) != C_FTSS_READABLE_LENGTH) { close(iHandle); throw UnixException(errno, "write"); }

		if (iIndexFile == 1)
		{
			// Write number of records (0)
			iDummy = iRecords;
			if (write(iHandle, &iDummy, sizeof(UINT_32)) != sizeof(UINT_32)) { close(iHandle); throw UnixException(errno, "write"); }
		}
	}

	//mmap files with written or checked headers
	if (iIndexFile == 1) {
		//IndexFile mmap 
		iIndexHandle = iHandle;
	}else{
		//DataFile mmap
		iDataHandle  = iHandle;
		lseek(iDataHandle, 0, SEEK_SET);
		iDataSize = lseek(iDataHandle, 0, SEEK_END);
	}
}

//
// Constructor
//
FileDocumentStorage::FileDocumentStorage(CCHAR_P szIndexFile, CCHAR_P szDataFile, const UINT_32 & iMode): iRecords(0), iDataSize(0)
{
	OpenStorage(szIndexFile, iMode, 1);
	
	try
	{
		OpenStorage(szDataFile, iMode, 0);
	}
	catch (LogicException & e)
	{
		close(iIndexHandle);
		throw;
	}
	catch (UnixException & e)
	{
		close(iIndexHandle);
		throw;
	}
	catch (...)
	{
		close(iIndexHandle);
		throw;
	}
}

//
// A Destructor
//
FileDocumentStorage::~FileDocumentStorage() throw()
{
	close(iIndexHandle);
	close(iDataHandle);
}

//
// Get document by specified ID
//
Document * FileDocumentStorage::GetDocument(const DocumentId_t & iDocumentId)
{
	
	if (iDocumentId >= DocumentId_t(iRecords))
	{
#ifdef _DEBUG
fprintf(stderr, "GetDocument: Invalid document ID = %d of %d\n", INT_32(iDocumentId), INT_32(iRecords));
#endif
		return NULL;
	}


	off_t iOffset = C_IDX_DATA_OFFSET + iDocumentId * sizeof(UINT_64);
	if (iOffset != lseek(iIndexHandle, iOffset, SEEK_SET)) { return NULL; }

	UINT_64 iDummy64 = 0;
	// Read offset from index
	if (read(iIndexHandle, &iDummy64, sizeof(UINT_64)) != sizeof(UINT_64)) { return NULL; }

	iOffset = iDummy64;
	if (iOffset != lseek(iDataHandle, iOffset, SEEK_SET)) { return NULL; }

	UINT_32 iDummy32 = 0;
	// Read size of document
	if (read(iDataHandle, &iDummy32, sizeof(UINT_32)) != sizeof(UINT_32)) { return NULL; }
	INT_32 iSize = iDummy32;

	void * vDocument = malloc(iSize);

	iSize -= sizeof(UINT_32);
	// Store document size
	((Document *)vDocument) -> size = iSize;

	// Read document
	//if (read(iDataHandle, (char *)vDocument + sizeof(UINT_32), iSize) != iSize) { free(vDocument); return NULL; }
	CHAR_P iDataHandleMMAPBuffer;
	// mmap pages with document body 
	if (
		(iDataHandleMMAPBuffer = 
		(CHAR_P) mmap(
			0,
			/* mmap size of document */
			((iSize + (iOffset + sizeof(UINT_32)) % sysconf(_SC_PAGE_SIZE)) / sysconf(_SC_PAGE_SIZE) + 1) * sysconf(_SC_PAGE_SIZE), 
			mmapFlags,
			MAP_PRIVATE,
			iDataHandle,
			/* mmap start page  */
			((iOffset + sizeof(UINT_32)) / sysconf(_SC_PAGE_SIZE)) *  sysconf(_SC_PAGE_SIZE))) 
		== MAP_FAILED){
		//fprintf(stderr, "MMAP FAIL %s \n", strerror(errno));
		throw UnixException(errno, "MMapFail");
	}

	if (memcpy((char *)vDocument + sizeof(UINT_32),
		iDataHandleMMAPBuffer +  (iOffset + sizeof(UINT_32)) % sysconf(_SC_PAGE_SIZE) ,
		iSize)	!= (CHAR_P)vDocument + sizeof(UINT_32)){free(vDocument); return NULL;}

	if  (munmap(iDataHandleMMAPBuffer, iSize / sysconf(_SC_PAGE_SIZE) + 1) == -1){
		throw UnixException(errno, "umapData");
	}

	return (Document *)vDocument;
}

//
// Store document information
//
DocumentId_t FileDocumentStorage::StoreDocument(const Document * pDocument)
{
	//fprintf(stderr, "\n\nStoreDocument Text=%s index_size=%d\n\n", pDocument->DocumentData(),  pDocument->index_size);
	return StoreDocumentParts(pDocument->parts, pDocument->parts_size, pDocument->GetDocumentIndex(), pDocument->index_size, (UCCHAR_P)pDocument->DocumentData(), pDocument->size - pDocument->index_size - pDocument->parts_size - 2/*i don't know why 2, but 3 doesn't correctly work*/ * sizeof(UINT_32) );
}

//
// Store document information
//
DocumentId_t FileDocumentStorage::StoreDocumentParts(const DocumentPart   * aParts,
                                                     const UINT_32        & iPartsSize,
                                                     const DocumentIndex  * aIndex,
                                                     const UINT_32        & iIndexSize,
                                                     const UCCHAR_P         sText,
                                                     const UINT_32        & iTextSize)
{
	UINT_64 iDummy64 = iDataSize;

	// Write new offset to index file
	lseek(iIndexHandle, 0, SEEK_END);
	if (write(iIndexHandle, &iDummy64, sizeof(UINT_64)) != sizeof(UINT_64)) { return (DocumentId_t)-1; }

	// Full size of document
	INT_32  iDummy32 = sizeof(UINT_32) + sizeof(UINT_32) + sizeof(UINT_32)+ iPartsSize + iIndexSize + iTextSize;
	iDataSize += iDummy32;

	// Write document size
	lseek(iDataHandle, 0, SEEK_END);
	if (write(iDataHandle, &iDummy32, sizeof(UINT_32)) != sizeof(UINT_32)) { return (DocumentId_t)-1; }

	// Write parts size
	iDummy32 = iPartsSize;
	if (write(iDataHandle, &iDummy32, sizeof(UINT_32)) != sizeof(UINT_32)) { return (DocumentId_t)-1; }

	// Write index size
	iDummy32 = iIndexSize;
	if (write(iDataHandle, &iDummy32, sizeof(UINT_32)) != sizeof(UINT_32)) { return (DocumentId_t)-1; }

	// Write parts
	iDummy32 = iPartsSize;
	if (write(iDataHandle, aParts, iDummy32) != iDummy32)                  { return (DocumentId_t)-1; }

	// Write index
	iDummy32 = iIndexSize;
	if (write(iDataHandle, aIndex, iDummy32) != iDummy32)                  { return (DocumentId_t)-1; }

	// Write data
	if (write(iDataHandle, sText, iTextSize) != INT_32(iTextSize))         { return (DocumentId_t)-1; }
	//fprintf(stderr, "sText=%s iTextSize=%d\n", sText, iTextSize);
return iRecords++;
}

//
// Flush buffers to the storage
//
void FileDocumentStorage::Sync()
{
	UINT_32 iDummy = iRecords;

#ifdef _DEBUG
fprintf(stderr, "Sync: records: %d\n", iDummy);
#endif
//	fprintf(stderr, "C_IDX_RECNUM_OFFSET=%u\n", C_IDX_RECNUM_OFFSET);
	if (lseek(iIndexHandle, C_IDX_RECNUM_OFFSET, SEEK_SET) != C_IDX_RECNUM_OFFSET) { throw UnixException(errno, "lseek"); }
	if (write(iIndexHandle, &iDummy, sizeof(UINT_32)) != sizeof(UINT_32)) { throw UnixException(errno, "write"); }
}

} // namespace FTSS
// End.
